/*
  提示：
  ws2812 8*8硬屏
  开发板选择 NodeMCU 1.0

*/

#include "config.h"


// handle WebSocket message
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
  switch (type) {
    case WStype_DISCONNECTED:
      Serial.printf("[%u] Disconnected!\n", num);
      break;
    case WStype_CONNECTED: {
        IPAddress ip = webSocket.remoteIP(num);
        //Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
        // send message to client
        webSocket.sendTXT(num, "Connected");
        configSync(num);//配置同步
      }
      break;
    case WStype_TEXT:
    {
      Serial.printf("[%u] get Text[%u].\n", num, length);
      strcpy(socketMessage,(char *)payload);
      if (socketMessage[0] == 'l')//切换风格
      {
          modeChange();
      }else if (socketMessage[0] == 'b')//亮度
      {
        if (socketMessage[1] == 'n')//亮度数值
        {
          int bn= ((int)socketMessage[2]-48)*100+((int)socketMessage[3]-48)*10+(int)socketMessage[4]-48;
          if(bn>=0&&bn<=255) brightnessNow = bn;
          matrix->setBrightness(brightnessNow);
          matrix->show();
        }else{
          brightnessChange();
        }
      }else if (socketMessage[0] == 'c')//颜色
      {
        if(socketMessage[1] == 'c'){
          clockChange();
        }else{
          colorChange();
        }
      }else if (socketMessage[0] == 's')//状态
      {
         statusSave();
      }else if (socketMessage[0] == 'p')//状态
      {
         pointChange();  
      }else if(socketMessage[0] == 'h'){//颜色设置
        int hh= ((int)socketMessage[2]-48)*100+((int)socketMessage[3]-48)*10+(int)socketMessage[4]-48;
        Serial.print("传入色值：");
        Serial.println(hh);
        if(hh>=0&&hh<=360){
            switch (socketMessage[1])
            {
            case 'h':
                hueh = hh;
                break;
            case 'm':
                huem = hh;
                break;
            case 's':
                hues = hh;
                break;          
            default:
                break;
            }
        }
      }else if (socketMessage[0] == 'e'){//emoji
        if(Mode == DRAW_MODE){
          String txt = String();
          Serial.println(*payload);
          uint8_t i = 0;
          for (uint8_t y = 0; y < yres; y++) {
            for (uint8_t x = 0; x < xres; x++) {
              uint8_t r = payload[y*xres*3+x*3+1]*2;
              uint8_t g = payload[y*xres*3+x*3+2]*2;
              uint8_t b = payload[y*xres*3+x*3+3]*2;
              matrix->drawPixel(x,y,matrix->Color(r,g,b));
              //Serial.printf("(%u, %u) color: %u, %u, %u\n", x, y, r, g, b);
            }
          }
          matrix->show(); // This sends the updated pixel color to the hardware.
        }
      }
    }
    break;
  }
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(16);
  // Check if this is first time EEPROM is used
  
  matrix->begin();
  matrix->setTextWrap(false);
  matrix->setBrightness(50);
  matrix->show(); // This sends the updated pixel color to the hardware.
  helloWorld();
  showWiFi();
  //WiFiManager
  WiFiManager wifiManager;
  wifiManager.autoConnect(WIFI_NAME);
  // start MDNS
  if (MDNS.begin(MDNS_NAME)) {
    Serial.println("MDNS responder started.");
  }
  button.attachClick(colorChange);
  button.attachDoubleClick(modeChange);
  button.attachMultiClick(statusSave);
  button.attachDuringLongPress(brightnessChange);
  // start web server
  // handle index
  server.on("/", []() {
    // send index.html
    server.send(200, "text/html", "<!DOCTYPE html><html><head> <meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> <meta name='viewport' content='user-scalable=no,initial-scale=2.0' /> <style> body { background: #000; color: #fff; text-align: center; font-size: small; } div { margin: 10px } </style></head><body> <div> <h3>Pick An Emoji</h3> <img id='emojis' onclick='clickEmoji(event);' src=''> </div> <div> <label id='out'></label> <canvas id='emoji' width='8' height='8'></canvas> </div> <div> <button onclick='sendCode(Point)'>屏幕方向</button> <button onclick='sendCode(Mode)'>模式切换</button> </div> <div> <span>亮度</span> <label><input id='bn' type='range' min='0' max='255' step='1' oninput='sendBrightness()'/></label>  </div> <div> <button onclick='sendCode(ClockMode)'>时钟样式</button> <button onclick='sendCode(Color)'>颜色变更</button> </div> <div> <span>小时</span> <label><input id='hueh' type='range' min='0' max='360' step='1' oninput='sendColor(hueh)'/></label>  </div> <div> <span>分钟</span> <label><input id='huem' type='range' min='0' max='360' step='1' oninput='sendColor(huem)'/></label>  </div> <div> <span>秒钟</span> <label><input id='hues' type='range' min='0' max='360' step='1' oninput='sendColor(hues)'/></label> </div> <div><button onclick='sendCode(Save)'>配置保存</button></div> <script> var Mode = 'l'; var Point = 'p'; var ClockMode = 'cc'; var Color = 'c'; var Save = 's'; var hueh = 'h'; var huem = 'm'; var hues = 's'; function nw() { return new WebSocket('ws://' + location.hostname + ':81/', ['arduino']); }  var ws = nw();  ws.onmessage = function (e) { console.log('Server: ', e.data);  if(e.data[0]=='{'){ console.log('json'); var jsonOb = eval('('+e.data+')'); var brightNess = typeof(jsonOb.brightNess)=='undefined'? 150:jsonOb.brightNess; var hueh = typeof(jsonOb.hueh)=='undefined'? 180:jsonOb.hueh; var huem = typeof(jsonOb.huem)=='undefined'? 180:jsonOb.huem; var hues = typeof(jsonOb.hues)=='undefined'? 180:jsonOb.hues; document.getElementById('bn').value=parseInt(brightNess); document.getElementById('hueh').value=parseInt(hueh); document.getElementById('huem').value=parseInt(huem); document.getElementById('hues').value=parseInt(hues);  } }; var sk = 0; function cc(c) { return String.fromCharCode(c / 2); } function clickEmoji(e) {  var xo = 3; var yo = 3; var xs = 13; var ys = 12; var x = e.offsetX; var y = e.offsetY;  var c = Math.round((x - xo - 4) / xs);  var r = Math.round((y - yo - 4) / ys);  document.getElementById('out').innerText = 'Selected [' + c + ', ' + r + ']';  var img = document.getElementById('emojis');  var ce = document.getElementById('emoji');  var ctx = ce.getContext('2d');  ctx.drawImage(img, -(xo + (xs * c)), -(yo + (ys * r)), 131, 122); var d = ctx.getImageData(0, 0, 8, 8).data;  var t = 'e';  for (var i = 0; i < 8; i++) { for(var j = 0; j < 8; j++){ var imageData = ctx.getImageData(j, i, 1, 1).data; t += cc(imageData[0]); t += cc(imageData[1]); t += cc(imageData[2]); } } ws.send(t, { binary: true }); } function sendCode(c){ console.log('发送命令：'+c); ws.send(c); } function sendBrightness() {  var Obrightness = document.getElementById('bn').value;  var oMessage = 'bn';  if (Obrightness.length == 1) {  oMessage = oMessage + '00';  } else if (Obrightness.length == 2) {  oMessage = oMessage + '0';  }  oMessage = oMessage + parseInt(Obrightness ? Obrightness : 255);  console.log('oMessage: ' + oMessage); ws.send(oMessage); } function sendColor(n){ var OHueValue = document.getElementById('hue'+n).value;  var oMessage = 'h'+n;  if (OHueValue.length == 1) {  oMessage = oMessage + '00';  } else if (OHueValue.length == 2) {  oMessage = oMessage + '0';  } oMessage = oMessage + parseInt(OHueValue ? OHueValue : 999);  console.log('oMessage: ' + oMessage); ws.send(oMessage);  } </script> </body> </html>");
    Serial.println("index.html sent.");
  });
  server.begin();

  // start webSocket server
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
  // Add service to MDNS
  MDNS.addService("http", "tcp", 80);
  MDNS.addService("ws", "tcp", 81);
  if (EEPROM.read(15) != 123) {  // not equal to 123
    Serial.println("未查到默认配置");
    Mode = 0;
    color_index = 0;
    brightnessNow = 50;
    clockMode = 1;
    point = 1;
  } else {  // if byte 256 is equal to 123
    Serial.println("存有默认配置");
    Mode = int(EEPROM.read(0));  // get previous lit leds before power failure
    color_index = int(EEPROM.read(1));
    brightnessNow = int(EEPROM.read(2));
    clockMode = int(EEPROM.read(3));
    point = int(EEPROM.read(4));
    hueh = int(EEPROM.read(5))*2;
    huem = int(EEPROM.read(6))*2;
    hues = int(EEPROM.read(7))*2;
  }
  matrix->setBrightness(brightnessNow);
  matrix->show();
  setPoint();
  ShowIP();
  sampling_period_us = round(1000000*(1.0/samplingFrequency)); //计算采样频率
  switchMode(true);
}


unsigned long lastTime = 0;
unsigned long lastColorTime = 0;
unsigned long lastLoopTime = 0;
unsigned int counter = 0;

//设置屏幕朝向
void setPoint(){
  matrix->clear();
  switch(point){
    case 1:
      matrix = new Adafruit_NeoMatrix(xres, yres, LED_PIN,
                            NEO_MATRIX_BOTTOM   + NEO_MATRIX_RIGHT  + 
                            NEO_MATRIX_ROWS   + NEO_MATRIX_PROGRESSIVE +
                            NEO_GRB + NEO_KHZ800);
      break;
    case 2:
      matrix = new Adafruit_NeoMatrix(xres, yres, LED_PIN,
                            NEO_MATRIX_TOP  + NEO_MATRIX_RIGHT  + 
                            NEO_MATRIX_COLUMNS   + NEO_MATRIX_ZIGZAG +
                            NEO_GRB + NEO_KHZ800);
      break;
    case 3:
      matrix = new Adafruit_NeoMatrix(xres, yres, LED_PIN,
                            NEO_MATRIX_TOP  + NEO_MATRIX_LEFT  + 
                            NEO_MATRIX_COLUMNS   + NEO_MATRIX_PROGRESSIVE +
                            NEO_GRB + NEO_KHZ800);
      break;
    case 4:
      matrix = new Adafruit_NeoMatrix(xres, yres, LED_PIN,
                            NEO_MATRIX_BOTTOM   + NEO_MATRIX_LEFT  + 
                            NEO_MATRIX_ROWS   + NEO_MATRIX_PROGRESSIVE +
                            NEO_GRB + NEO_KHZ800);
      break; 
  }
  matrix->setBrightness(brightnessNow);
  matrix->show();
}

void pointChange(){
  if (++point > 4)point = 1;//朝向变化
  Serial.println("变换朝向");
  Serial.println(point);
  setPoint();
}

//模式变更
void modeChange()
{
  matrix->clear();
  if (++Mode > MODE_MAX)Mode = 0;//模式变化
  Serial.println("按键切换模式");
  Serial.println(Mode);
  switchMode(true);
}
//时钟样式切换
void clockChange(){
  matrix->clear();
  if (++clockMode > 1)clockMode = 0;//时钟变化
  Serial.println("按键切换时钟");
  Serial.println(clockMode);
  switchMode(true);
}
//颜色模式变更
void colorChange()
{
  if (++color_index > 3)color_index = 0;//模式变化
  Serial.println("按键切换颜色");
  Serial.println(color_index);
  switchMode(true);
}
//亮度调节
void brightnessChange()
{
  unsigned long t = millis();
  if ((t - lastTime) > 500) {
    brightnessNow += 10;
    if(brightnessNow > 100){
      brightnessNow = 10;
    }
    matrix->setBrightness(brightnessNow);
    matrix->show();
    lastTime = millis();
  }
}


//RGB颜色数组https://www.sojson.com/rgb.html
const ColorRGB myRGBColorPalette[4][2] = {
  {ColorVarInit(255,102,0), ColorVarInit(30,144,255)},
  {ColorVarInit(255,255,0), ColorVarInit(255,0,77)},
  {ColorVarInit(0,255,0), ColorVarInit(148,0,211)},
  {ColorVarInit(0,255,255), ColorVarInit(255,0,255)}
};

//设置保存
void statusSave(){
  Serial.println("按键保存配置");
  if (EEPROM.read(15) != 123) {  // not equal to 123
    EEPROM.write(15, 123);  // write value 123 to byte 256
  }
  EEPROM.write(0, Mode);
  EEPROM.write(1, color_index);
  EEPROM.write(2, brightnessNow);
  EEPROM.write(3, clockMode);
  EEPROM.write(4, point);
  EEPROM.write(5, hueh/2);
  EEPROM.write(6, huem/2);
  EEPROM.write(7, hues/2);
  EEPROM.commit();
  int x = xres;
  for(int i=0;i<36;i++){
    matrix->clear();
    matrix->fillScreen(0);
    matrix->setCursor(x, 0);
    matrix->print(F("Saved"));
    matrix->setTextColor(matrix->Color(148,0,211));
    if(--x < -36) {
      x = matrix->width();
    }
    matrix->show();
    system_soft_wdt_feed();
    delay(70);
  }
  matrix->clear();
  switchMode(true);
}

//显示画板功能
void showDrawingBoard(bool implement) {

  unsigned long t = millis();
  if(implement){
    matrix->clear();
    showEmoji();
    matrix->show();
  }
  if ((t - lastTime) > 10 * 1000 | implement) {
    counter++;
    bool ping = (counter % 2);
    int i = webSocket.connectedClients(ping);
    Serial.printf("%d Connected websocket clients ping: %d\n", i, ping);
    lastTime = millis();
  }
}

void showEmoji(){
    for (uint8_t y = 0; y < yres; y++) {
          for (uint8_t x = 0; x < xres; x++) {
              uint8_t r = pgm_read_dword(&(emoji[y*xres*3+x*3]));
              uint8_t g = pgm_read_dword(&(emoji[y*xres*3+x*3+1]));
              uint8_t b = pgm_read_dword(&(emoji[y*xres*3+x*3+2]));
              matrix->drawPixel(x,y,matrix->Color(r,g,b));
          }
    }  
}

/**
 * @brief 显示时间
 * 
 * @param implement 
 */
void showTime(bool implement)
{
  timeClient.update();
  H = timeClient.getFormattedTime().substring(0, 2);
  M = timeClient.getFormattedTime().substring(3, 5);
  if ( millis() - lastColorTime > 300 | implement)
  {
    if(!implement){
      lastColorTime = millis();
    }
    switch(clockMode){
      case 0:
        colorLoop = colorLoop+1;
        if(colorLoop>54){
            colorLoop = 1;
        }
        showHours();
        showbittime(M,3);
        break;
      case 1:
        S = timeClient.getFormattedTime().substring(6, 8);
        binaryClock();
        break;
    }
    
    matrix->show();
  }
}

/**
 * @brief 显示日期
 * 
 * @param implement 
 */
void showMouthDay(bool implement) {
  if ( millis() - lastTime > 2000 | implement)
  {
    timeClient.update();
    D = timeClient.getDay();
    //将epochTime换算成年月日
    unsigned long epochTime = timeClient.getEpochTime();
    struct tm *ptm = gmtime((time_t *)&epochTime);
    monthDay = ptm->tm_mday;
    currentMonth = ptm->tm_mon + 1;
    Serial.println(monthDay);
    Serial.println(currentMonth);
    lastTime = millis();
    matrix->clear();
  }
  showbitnumber(monthDay,4,5,0,1);
  drawFastXLine(1,7,7,hsv2rgb(hueh, saturation-50, value-50));
  switch(D){
    case 0:
      drawFastXLine(7,7,1,hsv2rgb(hueh, saturation, value));
      break;
    default:
      drawFastXLine(D,7,1,hsv2rgb(hueh, saturation, value));
      break;
  }
  matrix->show();
}


void showLoopMode(bool implement){
  if ( millis() - lastLoopTime > 5000)
  {
    switch(LoopModeNow){
      case DATE_MODE:
        LoopModeNow = TIME_MODE;
        break;
      case TIME_MODE:
        LoopModeNow = DATE_MODE;
        break;
    }
    implement = true;
    lastLoopTime = millis();
    matrix->clear();
  }
  switch(LoopModeNow){
    case TIME_MODE:
      showTime(implement);
      break;
    case DATE_MODE:
      showMouthDay(implement);
      break;
  }
}


void binaryClock(){
  int h1,h2;
  int s1,s2;
  int m1,m2;
  if(H.toInt()<10){
    h1 = 0;
    h2 = (String(H.charAt(1)).toInt() + 1) - 1; 
  }else{
    h1 = (String(H.charAt(0)).toInt() + 1) - 1;
    h2 = (String(H.charAt(1)).toInt() + 1) - 1;
  }
  showBinary(h1, 0, hueh);
  showBinary(h2, 1, hueh);

  if(M.toInt()<10){
    m1 = 0;
    m2 = (String(M.charAt(1)).toInt() + 1) - 1; 
  }else{
    m1 = (String(M.charAt(0)).toInt() + 1) - 1;
    m2 = (String(M.charAt(1)).toInt() + 1) - 1;
  }
  showBinary(m1, 3, huem);
  showBinary(m2, 4, huem);

  if(S.toInt()<10){
    s1 = 0;
    s2 = (String(S.charAt(1)).toInt() + 1) - 1; 
  }else{
    s1 = (String(S.charAt(0)).toInt() + 1) - 1;
    s2 = (String(S.charAt(1)).toInt() + 1) - 1;
  }
  Serial.println(s1);
  Serial.println(s2);
  showBinary(s1, 6, hues);
  showBinary(s2, 7, hues);
  
}

void showBinary(int num, int x, int hue){
  for(int i=0;i<4;i++){
    if (String(binarydata[num].charAt(i)).toInt() != 0) {
      matrix->drawPixel(x,2+i,hsv2rgb(hue,100,100));
    } else {
      matrix->drawPixel(x,2+i,hsv2rgb(hue,60,30));
    }
  }
}

void showHours(){
  int hours = timeClient.getHours();
  uint16_t colorxy = hsv2rgb(hueh,100,100);
  if(hours>12){
    hours = hours-12;
  }
  drawFastXLineGXY(0,0,8,false);
  drawFastXLineGXY(0,1,8,false);
  int n = hours/3;
  int surplus = hours%3;
  switch(n){
    case 0:
      drawFastXLineGXY(1,0,surplus,true);
      break;
    case 1:
      drawFastXLineGXY(1,0,3,true);
      drawFastXLineGXY(5,0,surplus,true);
      break;
    case 2:
      drawFastXLineGXY(1,0,3,true);
      drawFastXLineGXY(5,0,3,true);
      drawFastXLineGXY(1,1,surplus,true);
      break;
    case 3:
      drawFastXLineGXY(1,0,3,true);
      drawFastXLineGXY(5,0,3,true);
      drawFastXLineGXY(1,1,3,true);
      drawFastXLineGXY(5,1,surplus,true);
      break;
    case 4:
      drawFastXLineGXY(1,0,3,true);
      drawFastXLineGXY(5,0,3,true);
      drawFastXLineGXY(1,1,3,true);
      drawFastXLineGXY(5,1,3,true);
      break;
  }
}

void showbittime(String nowtime, int y){
  if(nowtime.toInt()<10){
    showbitmap(bitdata20[(int)(0)],4,5, 0, y);
    showbitmap(bitdata20[(int)((String(nowtime.charAt(1)).toInt() + 1) - 1)], 4, 5, 4, y);
  }else{
    showbitmap(bitdata20[(int)((String(nowtime.charAt(0)).toInt() + 1) - 1)],4,5, 0, y);
    showbitmap(bitdata20[(int)((String(nowtime.charAt(1)).toInt() + 1) - 1)],4,5, 4, y);
  }
 }


void showbitmap(String bitrgbstr, int xlength, int ylength, int x, int y) {
  //Serial.println("bitrgbstr = " + bitrgbstr);
  for (int i = x; i < x+(xlength); i = i + (1)) {
    for(int j = y; j < y+(ylength); j = j + (1)){
      if (String(bitrgbstr.charAt(((j-y)*xlength+i-x))).toInt() != 0) {
        matrix->drawPixel(i,j,Gradient(i,j));
      } else {
        matrix->drawPixel(i,j,matrix->Color(0, 0, 0));
      }
    }
  }
}

void showFFT() {
  //Collect Samples
    getSamples();
    //Update Display
    displayUpdate();
    matrix->show();
    delay(1);
}

void getSamples(){
  microseconds = micros();
  for(int i = 0; i < SAMPLES; i++){
    vReal[i] = analogRead(MIC_IN);
    vImag[i] = 0;
    while(micros() - microseconds < sampling_period_us){
        //empty loop
    }
    microseconds += sampling_period_us;
  }
  bool reduce = false;
  if ((millis() - lastTime) > 25) {
    lastTime = millis();
    reduce = true;
  }
  //FFT
  FFT.Windowing(vReal, 1, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
  //Update Intensity Array
  for(int i = 0; i < xres; i++){
    vReal[i] = (vReal[i*Displacement+1]+vReal[i*Displacement+2])/2;
    Serial.println(vReal[i]);
    vReal[i] = constrain(vReal[i],0 ,1200);            // set max value for input data
    vReal[i] = map(vReal[i], freq_gain2[i]+50, 1200, 0, yres);        // map data to fit our display
    if(reduce){Intensity[i] --;}                      // Decrease displayed value
    if (vReal[i] > Intensity[i])          // Match displayed value to measured value
      Intensity[i] = vReal[i];
  }
}


void displayUpdate(){
  int color = 0;
  for(int i = 0; i < xres; i++){
    drawFastYLine(i,yres-Intensity[i],Intensity[i],hsv2rgb(color+5,100,100));
    drawFastYLine(i,0,yres-1-Intensity[i],matrix->Color(0,0,0));

    color += 350/xres;             // Increment the Hue to get the Rainbow
    if(color>350){
      color = 0;
    }
  } 
}

void switchMode(bool change){
  switch (Mode)
  {
    case DRAW_MODE:
      showDrawingBoard(change);
      break;
    case TIME_MODE:
      showTime(change);
      break;
    case DATE_MODE:
      showMouthDay(change);
      break;
    case LOOP_MODE:
      showLoopMode(change);
      break;
    case FFT_MODE:
      showFFT();
      break;
  }
}

void loop()
{
  button.tick();
  MDNS.update();
  webSocket.loop();
  server.handleClient();
  switchMode(false);
}
